21.1 设置
首先得为目标对象关联一个析构函数。SetFinalizer会通过接口内部的类型信息对目标对象和finalizer函数(参数数量、类型等)做出检查,确保它们符合要求。
mfinal.go
func SetFinalizer(obj interface{}, finalizer interface{}) { // 从接口获取类型和对象指针 e := (*eface)(unsafe.Pointer(&obj)) etyp := e._type ot := (*ptrtype)(unsafe.Pointer(etyp)) // 忽略 nil 对象 _, base, _ := findObject(e.data) if base nil { // 0-length objects are okay. if e.data unsafe.Pointer(&zerobase) { return } } // 获取 finalizer 函数信息 f := (*eface)(unsafe.Pointer(&finalizer)) ftyp := f._type // 如果 finalizer = nil,则移除析构函数 if ftyp == nil { systemstack(func() { removefinalizer(e.data) }) return } // 确保 finalizer 是函数 if ftyp.kind&kindMask != kindFunc { throw(“runtime.SetFinalizer: second argument is ” + *ftyp._string + ”, not a function”) } // 检查 finalizer 参数数量及其类型 ft := (functype)(unsafe.Pointer(ftyp)) ins := ([]_type)(unsafe.Pointer(&ft.in)) if ft.dotdotdot || len(ins) != 1 { throw(“runtime.SetFinalizer: cannot pass ” + *etyp._string + ” to finalizer ” + *ftyp._string) } fint := ins[0] switch { case fint etyp: // ok - 相同类型 goto okarg case fint.kind&kindMask kindPtr: goto okarg case fint.kind&kindMask == kindInterface: goto okarg } // 检查结果错误,抛出异常 throw(“runtime.SetFinalizer: cannot pass ” + *etyp._string + ” to finalizer ” + ftyp._string) okarg: // 计算返回参数大小 nret := uintptr(0) for _, t := range ([]_type)(unsafe.Pointer(&ft.out)) { nret = round(nret, uintptr(t.align)) + uintptr(t.size) } nret = round(nret, ptrSize) // 确保 finalizer goroutine 运行 createfing() // 不能重复设置 finalizer 函数 systemstack(func() { if !addfinalizer(e.data, (*funcval)(f.data), nret, fint, ot) { throw(“runtime.SetFinalizer: finalizer already set”) } }) }
析构函数会被打包成specialfinalizer对象。
mheap.go
type special struct { next *special // 链表 offset uint16 // 目标对象地址偏移量 kind byte // 类型 } type specialfinalizer struct { special special // 匿名嵌入 fn *funcval nret uintptr fint *_type ot *ptrtype } func addfinalizer(p unsafe.Pointer, f *funcval, nret uintptr, fint *type, ot *ptrtype) bool { // 从固定分配器创建 specialfinalizer s := (*specialfinalizer)(fixAlloc_Alloc(&mheap.specialfinalizeralloc)) s.special.kind = KindSpecialFinalizer s.fn = f s.nret = nret s.fint = fint s.ot = ot // 添加(注意,使用了匿名嵌入字段) if addspecial(p, &s.special) { return true } // 已经有 finalizer,释放当前 specialfinalizer fixAlloc_Free(&mheap.specialfinalizeralloc, (unsafe.Pointer)(s)) return false }
最终specialfinalizer被保存到span.specials链表。这里的算法很有意思,利用目标对象在span的地址偏移量作为去重和排序条件。如此,单个循环就可以完成去重判断和有序添加操作。
mheap.go
type mspan struct { specials *special // 按偏移量对链表排序 } func addspecial(p unsafe.Pointer, s *special) bool { // 找到目标对象所属 span,计算地址偏移量 span := mHeap_LookupMaybe(&mheap_, p) offset := uintptr(p) - uintptr(span.start<<_PageShift) kind := s.kind // 遍历 span.specials 链表, // 通过偏移量和 _KindSpecialFinalizer 检查是否已设置 finalizer t := &span.specials for { x := *t if x nil { break } // 已设置 if offset uintptr(x.offset) && kind x.kind { return false // already exists } // 因为 span.specials 按 offset 排序,所以没必要超出范围检查 if offset < uintptr(x.offset) || (offset uintptr(x.offset) && kind < x.kind) { break } t = &x.next } // 利用上面的循环中断,将 special 插入到链表合适的地方,保持有序 s.offset = uint16(offset) s.next = *t *t = s return true }
移除操作也只须用偏移量遍历span.specials链表即可完成。
mheap.go
func removespecial(p unsafe.Pointer, kind uint8) *special { // 查找所属 span,计算地址偏移量 span := mHeap_LookupMaybe(&mheap_, p) offset := uintptr(p) - uintptr(span.start<<_PageShift) // 遍历链表,移除 special t := &span.specials for { s := *t if s nil { break } if offset uintptr(s.offset) && kind == s.kind { *t = s.next return s } t = &s.next } return nil }